// Copyright (c) 2017-2020 Intel Corporation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#ifndef __MFXPLUGINPLUSPLUS_H
#define __MFXPLUGINPLUSPLUS_H

#include "mfxplugin.h"

// base class for MFXVideoUSER/MFXAudioUSER API

class MFXBaseUSER {
 public:
  explicit MFXBaseUSER(mfxSession session = NULL) : m_session(session) {}

  virtual ~MFXBaseUSER() {}

  virtual mfxStatus Register(mfxU32 type, const mfxPlugin* par) = 0;
  virtual mfxStatus Unregister(mfxU32 type) = 0;
  virtual mfxStatus ProcessFrameAsync(const mfxHDL* in,
                                      mfxU32 in_num,
                                      const mfxHDL* out,
                                      mfxU32 out_num,
                                      mfxSyncPoint* syncp) = 0;

 protected:
  mfxSession m_session;
};

// c++ wrapper over only 3 exposed functions from MFXVideoUSER module
class MFXVideoUSER : public MFXBaseUSER {
 public:
  explicit MFXVideoUSER(mfxSession session = NULL) : MFXBaseUSER(session) {}

  virtual mfxStatus Register(mfxU32 type, const mfxPlugin* par) {
    return MFXVideoUSER_Register(m_session, type, par);
  }
  virtual mfxStatus Unregister(mfxU32 type) {
    return MFXVideoUSER_Unregister(m_session, type);
  }
  virtual mfxStatus ProcessFrameAsync(const mfxHDL* in,
                                      mfxU32 in_num,
                                      const mfxHDL* out,
                                      mfxU32 out_num,
                                      mfxSyncPoint* syncp) {
    return MFXVideoUSER_ProcessFrameAsync(m_session, in, in_num, out, out_num,
                                          syncp);
  }
};

// c++ wrapper over only 3 exposed functions from MFXAudioUSER module
class MFXAudioUSER : public MFXBaseUSER {
 public:
  explicit MFXAudioUSER(mfxSession session = NULL) : MFXBaseUSER(session) {}

  virtual mfxStatus Register(mfxU32 type, const mfxPlugin* par) {
    return MFXAudioUSER_Register(m_session, type, par);
  }
  virtual mfxStatus Unregister(mfxU32 type) {
    return MFXAudioUSER_Unregister(m_session, type);
  }
  virtual mfxStatus ProcessFrameAsync(const mfxHDL* in,
                                      mfxU32 in_num,
                                      const mfxHDL* out,
                                      mfxU32 out_num,
                                      mfxSyncPoint* syncp) {
    return MFXAudioUSER_ProcessFrameAsync(m_session, in, in_num, out, out_num,
                                          syncp);
  }
};

// initialize mfxPlugin struct
class MFXPluginParam {
  mfxPluginParam m_param;

 public:
  MFXPluginParam(mfxU32 CodecId,
                 mfxU32 Type,
                 mfxPluginUID uid,
                 mfxThreadPolicy ThreadPolicy = MFX_THREADPOLICY_SERIAL,
                 mfxU32 MaxThreadNum = 1)
      : m_param() {
    m_param.PluginUID = uid;
    m_param.Type = Type;
    m_param.CodecId = CodecId;
    m_param.MaxThreadNum = MaxThreadNum;
    m_param.ThreadPolicy = ThreadPolicy;
  }
  operator const mfxPluginParam&() const { return m_param; }
  operator mfxPluginParam&() { return m_param; }
};

// common interface part for every plugin: decoder/encoder and generic
struct MFXPlugin {
  virtual ~MFXPlugin() {}
  // init function always required for any transform or codec plugins, for codec
  // plugins it maps to callback from MediaSDK for generic plugin application
  // should call it MediaSDK mfxPlugin API mapping
  virtual mfxStatus PluginInit(mfxCoreInterface* core) = 0;
  // release CoreInterface, and destroy plugin state, not destroy plugin
  // instance
  virtual mfxStatus PluginClose() = 0;
  virtual mfxStatus GetPluginParam(mfxPluginParam* par) = 0;
  virtual mfxStatus Execute(mfxThreadTask task, mfxU32 uid_p, mfxU32 uid_a) = 0;
  virtual mfxStatus FreeResources(mfxThreadTask task, mfxStatus sts) = 0;
  // destroy plugin due to shared module distribution model plugin wont support
  // virtual destructor
  virtual void Release() = 0;
  // release resources associated with current instance of plugin, but do not
  // release CoreInterface related resource set in pluginInit
  virtual mfxStatus Close() = 0;
  // communication protocol between particular version of plugin and application
  virtual mfxStatus SetAuxParams(void* auxParam, int auxParamSize) = 0;
};

// common extension interface that codec plugins should expose additionally to
// MFXPlugin
struct MFXCodecPlugin : MFXPlugin {
  virtual mfxStatus Init(mfxVideoParam* par) = 0;
  virtual mfxStatus QueryIOSurf(mfxVideoParam* par,
                                mfxFrameAllocRequest* in,
                                mfxFrameAllocRequest* out) = 0;
  virtual mfxStatus Query(mfxVideoParam* in, mfxVideoParam* out) = 0;
  virtual mfxStatus Reset(mfxVideoParam* par) = 0;
  virtual mfxStatus GetVideoParam(mfxVideoParam* par) = 0;
};

// common extension interface that audio codec plugins should expose
// additionally to MFXPlugin
struct MFXAudioCodecPlugin : MFXPlugin {
  virtual mfxStatus Init(mfxAudioParam* par) = 0;
  virtual mfxStatus Query(mfxAudioParam* in, mfxAudioParam* out) = 0;
  virtual mfxStatus QueryIOSize(mfxAudioParam* par,
                                mfxAudioAllocRequest* request) = 0;
  virtual mfxStatus Reset(mfxAudioParam* par) = 0;
  virtual mfxStatus GetAudioParam(mfxAudioParam* par) = 0;
};

// general purpose transform plugin interface, not a codec plugin
struct MFXGenericPlugin : MFXPlugin {
  virtual mfxStatus Init(mfxVideoParam* par) = 0;
  virtual mfxStatus QueryIOSurf(mfxVideoParam* par,
                                mfxFrameAllocRequest* in,
                                mfxFrameAllocRequest* out) = 0;
  virtual mfxStatus Submit(const mfxHDL* in,
                           mfxU32 in_num,
                           const mfxHDL* out,
                           mfxU32 out_num,
                           mfxThreadTask* task) = 0;
};

// decoder plugins may only support this interface
struct MFXDecoderPlugin : MFXCodecPlugin {
  virtual mfxStatus DecodeHeader(mfxBitstream* bs, mfxVideoParam* par) = 0;
  virtual mfxStatus GetPayload(mfxU64* ts, mfxPayload* payload) = 0;
  virtual mfxStatus DecodeFrameSubmit(mfxBitstream* bs,
                                      mfxFrameSurface1* surface_work,
                                      mfxFrameSurface1** surface_out,
                                      mfxThreadTask* task) = 0;
};

// audio decoder plugins may only support this interface
struct MFXAudioDecoderPlugin : MFXAudioCodecPlugin {
  virtual mfxStatus DecodeHeader(mfxBitstream* bs, mfxAudioParam* par) = 0;
  //    virtual mfxStatus GetPayload(mfxU64 *ts, mfxPayload *payload) = 0;
  virtual mfxStatus DecodeFrameSubmit(mfxBitstream* in,
                                      mfxAudioFrame* out,
                                      mfxThreadTask* task) = 0;
};

// encoder plugins may only support this interface
struct MFXEncoderPlugin : MFXCodecPlugin {
  virtual mfxStatus EncodeFrameSubmit(mfxEncodeCtrl* ctrl,
                                      mfxFrameSurface1* surface,
                                      mfxBitstream* bs,
                                      mfxThreadTask* task) = 0;
};

// audio encoder plugins may only support this interface
struct MFXAudioEncoderPlugin : MFXAudioCodecPlugin {
  virtual mfxStatus EncodeFrameSubmit(mfxAudioFrame* aFrame,
                                      mfxBitstream* out,
                                      mfxThreadTask* task) = 0;
};

// vpp plugins may only support this interface
struct MFXVPPPlugin : MFXCodecPlugin {
  virtual mfxStatus VPPFrameSubmit(mfxFrameSurface1* surface_in,
                                   mfxFrameSurface1* surface_out,
                                   mfxExtVppAuxData* aux,
                                   mfxThreadTask* task) = 0;
  virtual mfxStatus VPPFrameSubmitEx(mfxFrameSurface1* in,
                                     mfxFrameSurface1* surface_work,
                                     mfxFrameSurface1** surface_out,
                                     mfxThreadTask* task) = 0;
};

struct MFXEncPlugin : MFXCodecPlugin {
  virtual mfxStatus EncFrameSubmit(mfxENCInput* in,
                                   mfxENCOutput* out,
                                   mfxThreadTask* task) = 0;
};

class MFXCoreInterface {
 protected:
  mfxCoreInterface m_core;

 public:
  MFXCoreInterface() : m_core() {}
  MFXCoreInterface(const mfxCoreInterface& pCore) : m_core(pCore) {}

  MFXCoreInterface(const MFXCoreInterface& that) : m_core(that.m_core) {}
  MFXCoreInterface& operator=(const MFXCoreInterface& that) {
    m_core = that.m_core;
    return *this;
  }
  bool IsCoreSet() { return m_core.pthis != 0; }
  mfxStatus GetCoreParam(mfxCoreParam* par) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.GetCoreParam(m_core.pthis, par);
  }
  mfxStatus GetHandle(mfxHandleType type, mfxHDL* handle) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.GetHandle(m_core.pthis, type, handle);
  }
  mfxStatus IncreaseReference(mfxFrameData* fd) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.IncreaseReference(m_core.pthis, fd);
  }
  mfxStatus DecreaseReference(mfxFrameData* fd) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.DecreaseReference(m_core.pthis, fd);
  }
  mfxStatus CopyFrame(mfxFrameSurface1* dst, mfxFrameSurface1* src) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.CopyFrame(m_core.pthis, dst, src);
  }
  mfxStatus CopyBuffer(mfxU8* dst, mfxU32 size, mfxFrameSurface1* src) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.CopyBuffer(m_core.pthis, dst, size, src);
  }
  mfxStatus MapOpaqueSurface(mfxU32 num,
                             mfxU32 type,
                             mfxFrameSurface1** op_surf) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.MapOpaqueSurface(m_core.pthis, num, type, op_surf);
  }
  mfxStatus UnmapOpaqueSurface(mfxU32 num,
                               mfxU32 type,
                               mfxFrameSurface1** op_surf) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.UnmapOpaqueSurface(m_core.pthis, num, type, op_surf);
  }
  mfxStatus GetRealSurface(mfxFrameSurface1* op_surf, mfxFrameSurface1** surf) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.GetRealSurface(m_core.pthis, op_surf, surf);
  }
  mfxStatus GetOpaqueSurface(mfxFrameSurface1* surf,
                             mfxFrameSurface1** op_surf) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.GetOpaqueSurface(m_core.pthis, surf, op_surf);
  }
  mfxStatus CreateAccelerationDevice(mfxHandleType type, mfxHDL* handle) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.CreateAccelerationDevice(m_core.pthis, type, handle);
  }
  mfxFrameAllocator& FrameAllocator() { return m_core.FrameAllocator; }
  mfxStatus GetFrameHandle(mfxFrameData* fd, mfxHDL* handle) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.GetFrameHandle(m_core.pthis, fd, handle);
  }
  mfxStatus QueryPlatform(mfxPlatform* platform) {
    if (!IsCoreSet()) {
      return MFX_ERR_NULL_PTR;
    }
    return m_core.QueryPlatform(m_core.pthis, platform);
  }
};

/* Class adapter between "C" structure mfxPlugin and C++ interface MFXPlugin */

namespace detail {
template <class T>
class MFXPluginAdapterBase {
 protected:
  mfxPlugin m_mfxAPI;

 public:
  MFXPluginAdapterBase(T* plugin, mfxVideoCodecPlugin* pCodec = NULL)
      : m_mfxAPI() {
    SetupCallbacks(plugin, pCodec);
  }

  MFXPluginAdapterBase(T* plugin, mfxAudioCodecPlugin* pCodec) : m_mfxAPI() {
    SetupCallbacks(plugin, pCodec);
  }

  operator mfxPlugin() const { return m_mfxAPI; }
  void SetupCallbacks(T* plugin) {
    m_mfxAPI.pthis = plugin;
    m_mfxAPI.PluginInit = _PluginInit;
    m_mfxAPI.PluginClose = _PluginClose;
    m_mfxAPI.GetPluginParam = _GetPluginParam;
    m_mfxAPI.Submit = 0;
    m_mfxAPI.Execute = _Execute;
    m_mfxAPI.FreeResources = _FreeResources;
  }

  void SetupCallbacks(T* plugin, mfxVideoCodecPlugin* pCodec) {
    SetupCallbacks(plugin);
    m_mfxAPI.Video = pCodec;
  }

  void SetupCallbacks(T* plugin, mfxAudioCodecPlugin* pCodec) {
    SetupCallbacks(plugin);
    m_mfxAPI.Audio = pCodec;
  }

 private:
  static mfxStatus _PluginInit(mfxHDL pthis, mfxCoreInterface* core) {
    return reinterpret_cast<T*>(pthis)->PluginInit(core);
  }
  static mfxStatus _PluginClose(mfxHDL pthis) {
    return reinterpret_cast<T*>(pthis)->PluginClose();
  }
  static mfxStatus _GetPluginParam(mfxHDL pthis, mfxPluginParam* par) {
    return reinterpret_cast<T*>(pthis)->GetPluginParam(par);
  }
  static mfxStatus _Execute(mfxHDL pthis,
                            mfxThreadTask task,
                            mfxU32 thread_id,
                            mfxU32 call_count) {
    return reinterpret_cast<T*>(pthis)->Execute(task, thread_id, call_count);
  }
  static mfxStatus _FreeResources(mfxHDL pthis,
                                  mfxThreadTask task,
                                  mfxStatus sts) {
    return reinterpret_cast<T*>(pthis)->FreeResources(task, sts);
  }
};

template <class T>
class MFXCodecPluginAdapterBase : public MFXPluginAdapterBase<T> {
 protected:
  // stub to feed mediasdk plugin API
  mfxVideoCodecPlugin m_codecPlg;

 public:
  MFXCodecPluginAdapterBase(T* pCodecPlg)
      : MFXPluginAdapterBase<T>(pCodecPlg, &m_codecPlg), m_codecPlg() {
    m_codecPlg.Query = _Query;
    m_codecPlg.QueryIOSurf = _QueryIOSurf;
    m_codecPlg.Init = _Init;
    m_codecPlg.Reset = _Reset;
    m_codecPlg.Close = _Close;
    m_codecPlg.GetVideoParam = _GetVideoParam;
  }
  MFXCodecPluginAdapterBase(const MFXCodecPluginAdapterBase<T>& that)
      : MFXPluginAdapterBase<T>(reinterpret_cast<T*>(that.m_mfxAPI.pthis),
                                &m_codecPlg),
        m_codecPlg() {
    SetupCallbacks();
  }
  MFXCodecPluginAdapterBase<T>& operator=(
      const MFXCodecPluginAdapterBase<T>& that) {
    MFXPluginAdapterBase<T>::SetupCallbacks(
        reinterpret_cast<T*>(that.m_mfxAPI.pthis), &m_codecPlg);
    SetupCallbacks();
    return *this;
  }

 private:
  void SetupCallbacks() {
    m_codecPlg.Query = _Query;
    m_codecPlg.QueryIOSurf = _QueryIOSurf;
    m_codecPlg.Init = _Init;
    m_codecPlg.Reset = _Reset;
    m_codecPlg.Close = _Close;
    m_codecPlg.GetVideoParam = _GetVideoParam;
  }
  static mfxStatus _Query(mfxHDL pthis, mfxVideoParam* in, mfxVideoParam* out) {
    return reinterpret_cast<T*>(pthis)->Query(in, out);
  }
  static mfxStatus _QueryIOSurf(mfxHDL pthis,
                                mfxVideoParam* par,
                                mfxFrameAllocRequest* in,
                                mfxFrameAllocRequest* out) {
    return reinterpret_cast<T*>(pthis)->QueryIOSurf(par, in, out);
  }
  static mfxStatus _Init(mfxHDL pthis, mfxVideoParam* par) {
    return reinterpret_cast<T*>(pthis)->Init(par);
  }
  static mfxStatus _Reset(mfxHDL pthis, mfxVideoParam* par) {
    return reinterpret_cast<T*>(pthis)->Reset(par);
  }
  static mfxStatus _Close(mfxHDL pthis) {
    return reinterpret_cast<T*>(pthis)->Close();
  }
  static mfxStatus _GetVideoParam(mfxHDL pthis, mfxVideoParam* par) {
    return reinterpret_cast<T*>(pthis)->GetVideoParam(par);
  }
};

template <class T>
class MFXAudioCodecPluginAdapterBase : public MFXPluginAdapterBase<T> {
 protected:
  // stub to feed mediasdk plugin API
  mfxAudioCodecPlugin m_codecPlg;

 public:
  MFXAudioCodecPluginAdapterBase(T* pCodecPlg)
      : MFXPluginAdapterBase<T>(pCodecPlg, &m_codecPlg), m_codecPlg() {
    m_codecPlg.Query = _Query;
    m_codecPlg.QueryIOSize = _QueryIOSize;
    m_codecPlg.Init = _Init;
    m_codecPlg.Reset = _Reset;
    m_codecPlg.Close = _Close;
    m_codecPlg.GetAudioParam = _GetAudioParam;
  }
  MFXAudioCodecPluginAdapterBase(const MFXCodecPluginAdapterBase<T>& that)
      : MFXPluginAdapterBase<T>(reinterpret_cast<T*>(that.m_mfxAPI.pthis),
                                &m_codecPlg),
        m_codecPlg() {
    SetupCallbacks();
  }
  MFXAudioCodecPluginAdapterBase<T>& operator=(
      const MFXAudioCodecPluginAdapterBase<T>& that) {
    MFXPluginAdapterBase<T>::SetupCallbacks(
        reinterpret_cast<T*>(that.m_mfxAPI.pthis), &m_codecPlg);
    SetupCallbacks();
    return *this;
  }

 private:
  void SetupCallbacks() {
    m_codecPlg.Query = _Query;
    m_codecPlg.QueryIOSize = _QueryIOSize;
    m_codecPlg.Init = _Init;
    m_codecPlg.Reset = _Reset;
    m_codecPlg.Close = _Close;
    m_codecPlg.GetAudioParam = _GetAudioParam;
  }
  static mfxStatus _Query(mfxHDL pthis, mfxAudioParam* in, mfxAudioParam* out) {
    return reinterpret_cast<T*>(pthis)->Query(in, out);
  }
  static mfxStatus _QueryIOSize(mfxHDL pthis,
                                mfxAudioParam* par,
                                mfxAudioAllocRequest* request) {
    return reinterpret_cast<T*>(pthis)->QueryIOSize(par, request);
  }
  static mfxStatus _Init(mfxHDL pthis, mfxAudioParam* par) {
    return reinterpret_cast<T*>(pthis)->Init(par);
  }
  static mfxStatus _Reset(mfxHDL pthis, mfxAudioParam* par) {
    return reinterpret_cast<T*>(pthis)->Reset(par);
  }
  static mfxStatus _Close(mfxHDL pthis) {
    return reinterpret_cast<T*>(pthis)->Close();
  }
  static mfxStatus _GetAudioParam(mfxHDL pthis, mfxAudioParam* par) {
    return reinterpret_cast<T*>(pthis)->GetAudioParam(par);
  }
};

template <class T>
struct MFXPluginAdapterInternal {};
template <>
class MFXPluginAdapterInternal<MFXGenericPlugin>
    : public MFXPluginAdapterBase<MFXGenericPlugin> {
 public:
  MFXPluginAdapterInternal(MFXGenericPlugin* pPlugin)
      : MFXPluginAdapterBase<MFXGenericPlugin>(pPlugin) {
    m_mfxAPI.Submit = _Submit;
  }
  MFXPluginAdapterInternal(const MFXPluginAdapterInternal& that)
      : MFXPluginAdapterBase<MFXGenericPlugin>(that) {
    m_mfxAPI.Submit = that._Submit;
  }
  MFXPluginAdapterInternal<MFXGenericPlugin>& operator=(
      const MFXPluginAdapterInternal<MFXGenericPlugin>& that) {
    MFXPluginAdapterBase<MFXGenericPlugin>::operator=(that);
    m_mfxAPI.Submit = that._Submit;
    return *this;
  }

 private:
  static mfxStatus _Submit(mfxHDL pthis,
                           const mfxHDL* in,
                           mfxU32 in_num,
                           const mfxHDL* out,
                           mfxU32 out_num,
                           mfxThreadTask* task) {
    return reinterpret_cast<MFXGenericPlugin*>(pthis)->Submit(in, in_num, out,
                                                              out_num, task);
  }
};

template <>
class MFXPluginAdapterInternal<MFXDecoderPlugin>
    : public MFXCodecPluginAdapterBase<MFXDecoderPlugin> {
 public:
  MFXPluginAdapterInternal(MFXDecoderPlugin* pPlugin)
      : MFXCodecPluginAdapterBase<MFXDecoderPlugin>(pPlugin) {
    SetupCallbacks();
  }

  MFXPluginAdapterInternal(const MFXPluginAdapterInternal& that)
      : MFXCodecPluginAdapterBase<MFXDecoderPlugin>(that) {
    SetupCallbacks();
  }

  MFXPluginAdapterInternal<MFXDecoderPlugin>& operator=(
      const MFXPluginAdapterInternal<MFXDecoderPlugin>& that) {
    MFXCodecPluginAdapterBase<MFXDecoderPlugin>::operator=(that);
    SetupCallbacks();
    return *this;
  }

 private:
  void SetupCallbacks() {
    m_codecPlg.DecodeHeader = _DecodeHeader;
    m_codecPlg.GetPayload = _GetPayload;
    m_codecPlg.DecodeFrameSubmit = _DecodeFrameSubmit;
  }
  static mfxStatus _DecodeHeader(mfxHDL pthis,
                                 mfxBitstream* bs,
                                 mfxVideoParam* par) {
    return reinterpret_cast<MFXDecoderPlugin*>(pthis)->DecodeHeader(bs, par);
  }
  static mfxStatus _GetPayload(mfxHDL pthis, mfxU64* ts, mfxPayload* payload) {
    return reinterpret_cast<MFXDecoderPlugin*>(pthis)->GetPayload(ts, payload);
  }
  static mfxStatus _DecodeFrameSubmit(mfxHDL pthis,
                                      mfxBitstream* bs,
                                      mfxFrameSurface1* surface_work,
                                      mfxFrameSurface1** surface_out,
                                      mfxThreadTask* task) {
    return reinterpret_cast<MFXDecoderPlugin*>(pthis)->DecodeFrameSubmit(
        bs, surface_work, surface_out, task);
  }
};

template <>
class MFXPluginAdapterInternal<MFXAudioDecoderPlugin>
    : public MFXAudioCodecPluginAdapterBase<MFXAudioDecoderPlugin> {
 public:
  MFXPluginAdapterInternal(MFXAudioDecoderPlugin* pPlugin)
      : MFXAudioCodecPluginAdapterBase<MFXAudioDecoderPlugin>(pPlugin) {
    SetupCallbacks();
  }

  MFXPluginAdapterInternal(const MFXPluginAdapterInternal& that)
      : MFXAudioCodecPluginAdapterBase<MFXAudioDecoderPlugin>(that) {
    SetupCallbacks();
  }

  MFXPluginAdapterInternal<MFXAudioDecoderPlugin>& operator=(
      const MFXPluginAdapterInternal<MFXAudioDecoderPlugin>& that) {
    MFXAudioCodecPluginAdapterBase<MFXAudioDecoderPlugin>::operator=(that);
    SetupCallbacks();
    return *this;
  }

 private:
  void SetupCallbacks() {
    m_codecPlg.DecodeHeader = _DecodeHeader;
    m_codecPlg.DecodeFrameSubmit = _DecodeFrameSubmit;
  }
  static mfxStatus _DecodeHeader(mfxHDL pthis,
                                 mfxBitstream* bs,
                                 mfxAudioParam* par) {
    return reinterpret_cast<MFXAudioDecoderPlugin*>(pthis)->DecodeHeader(bs,
                                                                         par);
  }
  static mfxStatus _DecodeFrameSubmit(mfxHDL pthis,
                                      mfxBitstream* in,
                                      mfxAudioFrame* out,
                                      mfxThreadTask* task) {
    return reinterpret_cast<MFXAudioDecoderPlugin*>(pthis)->DecodeFrameSubmit(
        in, out, task);
  }
};

template <>
class MFXPluginAdapterInternal<MFXEncoderPlugin>
    : public MFXCodecPluginAdapterBase<MFXEncoderPlugin> {
 public:
  MFXPluginAdapterInternal(MFXEncoderPlugin* pPlugin)
      : MFXCodecPluginAdapterBase<MFXEncoderPlugin>(pPlugin) {
    m_codecPlg.EncodeFrameSubmit = _EncodeFrameSubmit;
  }
  MFXPluginAdapterInternal(const MFXPluginAdapterInternal& that)
      : MFXCodecPluginAdapterBase<MFXEncoderPlugin>(that) {
    m_codecPlg.EncodeFrameSubmit = _EncodeFrameSubmit;
  }

  MFXPluginAdapterInternal<MFXEncoderPlugin>& operator=(
      const MFXPluginAdapterInternal<MFXEncoderPlugin>& that) {
    MFXCodecPluginAdapterBase<MFXEncoderPlugin>::operator=(that);
    m_codecPlg.EncodeFrameSubmit = _EncodeFrameSubmit;
    return *this;
  }

 private:
  static mfxStatus _EncodeFrameSubmit(mfxHDL pthis,
                                      mfxEncodeCtrl* ctrl,
                                      mfxFrameSurface1* surface,
                                      mfxBitstream* bs,
                                      mfxThreadTask* task) {
    return reinterpret_cast<MFXEncoderPlugin*>(pthis)->EncodeFrameSubmit(
        ctrl, surface, bs, task);
  }
};

template <>
class MFXPluginAdapterInternal<MFXAudioEncoderPlugin>
    : public MFXAudioCodecPluginAdapterBase<MFXAudioEncoderPlugin> {
 public:
  MFXPluginAdapterInternal(MFXAudioEncoderPlugin* pPlugin)
      : MFXAudioCodecPluginAdapterBase<MFXAudioEncoderPlugin>(pPlugin) {
    SetupCallbacks();
  }

  MFXPluginAdapterInternal(const MFXPluginAdapterInternal& that)
      : MFXAudioCodecPluginAdapterBase<MFXAudioEncoderPlugin>(that) {
    SetupCallbacks();
  }

  MFXPluginAdapterInternal<MFXAudioEncoderPlugin>& operator=(
      const MFXPluginAdapterInternal<MFXAudioEncoderPlugin>& that) {
    MFXAudioCodecPluginAdapterBase<MFXAudioEncoderPlugin>::operator=(that);
    SetupCallbacks();
    return *this;
  }

 private:
  void SetupCallbacks() { m_codecPlg.EncodeFrameSubmit = _EncodeFrameSubmit; }
  static mfxStatus _EncodeFrameSubmit(mfxHDL pthis,
                                      mfxAudioFrame* aFrame,
                                      mfxBitstream* out,
                                      mfxThreadTask* task) {
    return reinterpret_cast<MFXAudioEncoderPlugin*>(pthis)->EncodeFrameSubmit(
        aFrame, out, task);
  }
};

template <>
class MFXPluginAdapterInternal<MFXEncPlugin>
    : public MFXCodecPluginAdapterBase<MFXEncPlugin> {
 public:
  MFXPluginAdapterInternal(MFXEncPlugin* pPlugin)
      : MFXCodecPluginAdapterBase<MFXEncPlugin>(pPlugin) {
    m_codecPlg.ENCFrameSubmit = _ENCFrameSubmit;
  }
  MFXPluginAdapterInternal(const MFXPluginAdapterInternal& that)
      : MFXCodecPluginAdapterBase<MFXEncPlugin>(that) {
    m_codecPlg.ENCFrameSubmit = _ENCFrameSubmit;
  }

  MFXPluginAdapterInternal<MFXEncPlugin>& operator=(
      const MFXPluginAdapterInternal<MFXEncPlugin>& that) {
    MFXCodecPluginAdapterBase<MFXEncPlugin>::operator=(that);
    m_codecPlg.ENCFrameSubmit = _ENCFrameSubmit;
    return *this;
  }

 private:
  static mfxStatus _ENCFrameSubmit(mfxHDL pthis,
                                   mfxENCInput* in,
                                   mfxENCOutput* out,
                                   mfxThreadTask* task) {
    return reinterpret_cast<MFXEncPlugin*>(pthis)->EncFrameSubmit(in, out,
                                                                  task);
  }
};

template <>
class MFXPluginAdapterInternal<MFXVPPPlugin>
    : public MFXCodecPluginAdapterBase<MFXVPPPlugin> {
 public:
  MFXPluginAdapterInternal(MFXVPPPlugin* pPlugin)
      : MFXCodecPluginAdapterBase<MFXVPPPlugin>(pPlugin) {
    SetupCallbacks();
  }
  MFXPluginAdapterInternal(const MFXPluginAdapterInternal& that)
      : MFXCodecPluginAdapterBase<MFXVPPPlugin>(that) {
    SetupCallbacks();
  }

  MFXPluginAdapterInternal<MFXVPPPlugin>& operator=(
      const MFXPluginAdapterInternal<MFXVPPPlugin>& that) {
    MFXCodecPluginAdapterBase<MFXVPPPlugin>::operator=(that);
    SetupCallbacks();
    return *this;
  }

 private:
  void SetupCallbacks() {
    m_codecPlg.VPPFrameSubmit = _VPPFrameSubmit;
    m_codecPlg.VPPFrameSubmitEx = _VPPFrameSubmitEx;
  }
  static mfxStatus _VPPFrameSubmit(mfxHDL pthis,
                                   mfxFrameSurface1* surface_in,
                                   mfxFrameSurface1* surface_out,
                                   mfxExtVppAuxData* aux,
                                   mfxThreadTask* task) {
    return reinterpret_cast<MFXVPPPlugin*>(pthis)->VPPFrameSubmit(
        surface_in, surface_out, aux, task);
  }
  static mfxStatus _VPPFrameSubmitEx(mfxHDL pthis,
                                     mfxFrameSurface1* surface_in,
                                     mfxFrameSurface1* surface_work,
                                     mfxFrameSurface1** surface_out,
                                     mfxThreadTask* task) {
    return reinterpret_cast<MFXVPPPlugin*>(pthis)->VPPFrameSubmitEx(
        surface_in, surface_work, surface_out, task);
  }
};
}  // namespace detail

/* adapter for particular plugin type*/
template <class T>
class MFXPluginAdapter {
 public:
  detail::MFXPluginAdapterInternal<T> m_Adapter;

  operator mfxPlugin() const { return m_Adapter.operator mfxPlugin(); }

  MFXPluginAdapter(T* pPlugin = NULL) : m_Adapter(pPlugin) {}
};

template <class T>
inline MFXPluginAdapter<T> make_mfx_plugin_adapter(T* pPlugin) {
  MFXPluginAdapter<T> adapt(pPlugin);
  return adapt;
}

#endif  // __MFXPLUGINPLUSPLUS_H
